name = "cargo-new"
test = false
+[[bin]]
+name = "cargo-doc"
+test = false
+
[[test]]
name = "tests"
--- /dev/null
+#![feature(phase)]
+
+#[phase(plugin, link)]
+extern crate cargo;
+extern crate serialize;
+
+#[phase(plugin, link)]
+extern crate hammer;
+
+use std::os;
+
+use cargo::ops;
+use cargo::{execute_main_without_stdin};
+use cargo::core::{MultiShell};
+use cargo::util::{CliResult, CliError};
+use cargo::util::important_paths::find_project_manifest;
+
+#[deriving(PartialEq,Clone,Decodable)]
+struct Options {
+ manifest_path: Option<String>,
+ jobs: Option<uint>,
+ update: bool,
+ no_deps: bool,
+}
+
+hammer_config!(Options "Build the package's documentation", |c| {
+ c.short("jobs", 'j').short("update", 'u')
+})
+
+fn main() {
+ execute_main_without_stdin(execute);
+}
+
+fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
+ let root = match options.manifest_path {
+ Some(path) => Path::new(path),
+ None => try!(find_project_manifest(&os::getcwd(), "Cargo.toml")
+ .map_err(|_| {
+ CliError::new("Could not find Cargo.toml in this \
+ directory or any parent directory",
+ 102)
+ }))
+ };
+
+ let mut doc_opts = ops::DocOptions {
+ all: !options.no_deps,
+ compile_opts: ops::CompileOptions {
+ update: options.update,
+ env: if options.no_deps {"doc"} else {"doc-all"},
+ shell: shell,
+ jobs: options.jobs,
+ target: None,
+ },
+ };
+
+ try!(ops::doc(&root, &mut doc_opts).map_err(|err| {
+ CliError::from_boxed(err, 101)
+ }));
+
+ Ok(None)
+}
+
println!(" run # build and execute src/main.rs");
println!(" version # displays the version of cargo");
println!(" new # create a new cargo project");
+ println!(" doc # build project's rustdoc documentation");
println!("");
authors: Vec<String>,
targets: Vec<Target>,
target_dir: Path,
+ doc_dir: Path,
sources: Vec<SourceId>,
build: Vec<String>,
unused_keys: Vec<String>,
authors: Vec<String>,
targets: Vec<Target>,
target_dir: String,
+ doc_dir: String,
build: Option<Vec<String>>,
}
authors: self.authors.clone(),
targets: self.targets.clone(),
target_dir: self.target_dir.display().to_string(),
+ doc_dir: self.doc_dir.display().to_string(),
build: if self.build.len() == 0 { None } else { Some(self.build.clone()) },
}.encode(s)
}
}
}
+ pub fn default_doc() -> Profile {
+ Profile {
+ env: "doc".to_string(),
+ opt_level: 0,
+ debug: false,
+ test: false,
+ dest: Some("doc-build".to_string()),
+ plugin: false,
+ }
+ }
+
pub fn is_compile(&self) -> bool {
self.env.as_slice() == "compile"
}
+ pub fn is_doc(&self) -> bool {
+ self.env.as_slice() == "doc"
+ }
+
pub fn is_test(&self) -> bool {
self.test
}
impl Manifest {
pub fn new(summary: &Summary, targets: &[Target],
- target_dir: &Path, sources: Vec<SourceId>,
+ target_dir: &Path, doc_dir: &Path, sources: Vec<SourceId>,
build: Vec<String>) -> Manifest {
Manifest {
summary: summary.clone(),
authors: Vec::new(),
targets: Vec::from_slice(targets),
target_dir: target_dir.clone(),
+ doc_dir: doc_dir.clone(),
sources: sources,
build: build,
unused_keys: Vec::new(),
&self.target_dir
}
+ pub fn get_doc_dir(&self) -> &Path {
+ &self.doc_dir
+ }
+
pub fn get_source_ids(&self) -> &[SourceId] {
self.sources.as_slice()
}
use std::io::fs::{rmdir_recursive};
-use core::{SourceId};
+
+use core::source::Source;
+use sources::PathSource;
use util::{CargoResult, human, ChainError};
-use ops::{read_manifest};
-use std::io::{File};
-use util::toml::{project_layout};
/// Cleans the project from build artifacts.
pub fn clean(manifest_path: &Path) -> CargoResult<()> {
- let mut file = try!(File::open(manifest_path));
- let data = try!(file.read_to_end());
- let layout = project_layout(&manifest_path.dir_path());
- let (manifest, _) = try!(read_manifest(data.as_slice(),
- layout,
- &SourceId::for_path(manifest_path)));
+ let mut src = PathSource::for_path(&manifest_path.dir_path());
+ try!(src.update());
+ let root = try!(src.get_root_package());
+ let manifest = root.get_manifest();
let build_dir = manifest.get_target_dir();
-
if build_dir.exists() {
- rmdir_recursive(build_dir).chain_error(|| human("Could not remove build directory"))
- } else {
- Ok(())
+ try!(rmdir_recursive(build_dir).chain_error(|| {
+ human("Could not remove build directory")
+ }))
}
+
+ let doc_dir = manifest.get_doc_dir();
+ if doc_dir.exists() {
+ try!(rmdir_recursive(doc_dir).chain_error(|| {
+ human("Could not remove documentation directory")
+ }))
+ }
+
+ Ok(())
}
debug!("packages={}", packages);
let targets = package.get_targets().iter().filter(|target| {
- target.get_profile().get_env() == env
+ match env {
+ // doc-all == document everything, so look for doc targets
+ "doc" | "doc-all" => target.get_profile().get_env() == "doc",
+ env => target.get_profile().get_env() == env,
+ }
}).collect::<Vec<&Target>>();
let mut config = try!(Config::new(*shell, update, jobs, target));
--- /dev/null
+use std::io::fs;
+
+use ops;
+use util::CargoResult;
+use core::source::Source;
+use sources::PathSource;
+
+pub struct DocOptions<'a> {
+ pub all: bool,
+ pub compile_opts: ops::CompileOptions<'a>,
+}
+
+pub fn doc(manifest_path: &Path,
+ options: &mut DocOptions) -> CargoResult<()> {
+ let mut src = PathSource::for_path(&manifest_path.dir_path());
+ try!(src.update());
+ let root = try!(src.get_root_package());
+ let output = root.get_manifest().get_target_dir().join("doc");
+ let _ = fs::rmdir_recursive(&output);
+ try!(ops::compile(manifest_path, &mut options.compile_opts));
+ Ok(())
+}
pub fn is_relevant_target(&self, target: &Target) -> bool {
target.is_lib() && match self.env {
- "test" => target.get_profile().is_compile(),
+ "doc" | "test" => target.get_profile().is_compile(),
+ // doc-all == document everything, so look for doc targets and
+ // compile targets in dependencies
+ "doc-all" => target.get_profile().is_compile() ||
+ target.get_profile().is_doc(),
_ => target.get_profile().get_env() == self.env,
}
}
}
for &target in targets.iter() {
+ if target.get_profile().is_doc() { continue }
let layout = cx.layout(target.get_profile().is_plugin());
for filename in cx.target_filenames(target).iter() {
let filename = filename.as_slice();
pub fn old_native(&self, pkg: &Package) -> Path {
self.root.old_native(pkg)
}
+
+ pub fn proxy(&self) -> &'a Layout { self.root }
}
// interdependencies.
let (mut libs, mut bins) = (Vec::new(), Vec::new());
for &target in targets.iter() {
- let req = cx.get_requirement(pkg, target);
- let jobs = rustc(pkg, target, cx, req);
+ let jobs = if target.get_profile().is_doc() {
+ vec![rustdoc(pkg, target, cx)]
+ } else {
+ let req = cx.get_requirement(pkg, target);
+ rustc(pkg, target, cx, req)
+ };
if target.is_lib() {
libs.push_all_move(jobs);
} else {
}
}
+
+fn rustdoc(package: &Package, target: &Target, cx: &mut Context) -> Job {
+ // Can't document binaries, but they have a doc target listed so we can
+ // build documentation of dependencies even when `cargo doc` is run.
+ if target.is_bin() {
+ return Job::new(proc() Ok(Vec::new()))
+ }
+
+ let pkg_root = package.get_root();
+ let cx_root = cx.layout(false).proxy().dest().dir_path().join("doc");
+ let rustdoc = util::process("rustdoc").cwd(pkg_root.clone());
+ let rustdoc = rustdoc.arg(target.get_src_path())
+ .arg("-o").arg(cx_root)
+ .arg("--crate-name").arg(target.get_name());
+ let rustdoc = build_deps_args(rustdoc, target, package, cx, false);
+
+ log!(5, "commands={}", rustdoc);
+
+ let _ = cx.config.shell().verbose(|shell| {
+ shell.status("Running", rustdoc.to_string())
+ });
+
+ let primary = cx.primary;
+ let name = package.get_name().to_string();
+ Job::new(proc() {
+ if primary {
+ try!(rustdoc.exec().chain_error(|| {
+ human(format!("Could not document `{}`.", name))
+ }))
+ } else {
+ try!(rustdoc.exec_with_output().and(Ok(())).map_err(|err| {
+ caused_human(format!("Could not document `{}`.\n{}",
+ name, err.output().unwrap()), err)
+ }))
+ }
+ Ok(Vec::new())
+ })
+}
fn build_base_args(mut cmd: ProcessBuilder,
target: &Target,
crate_types: &[&str]) -> ProcessBuilder {
pub use self::cargo_rustc::compile_targets;
pub use self::cargo_run::run;
pub use self::cargo_new::{new, NewOptions};
+pub use self::cargo_doc::{doc, DocOptions};
mod cargo_clean;
mod cargo_compile;
mod cargo_rustc;
mod cargo_run;
mod cargo_new;
+mod cargo_doc;
path: Some(TomlPath(lib.clone())),
test: None,
plugin: None,
+ doc: None,
}]
}).unwrap_or(Vec::new())
}
path: Some(TomlPath(bin.clone())),
test: None,
plugin: None,
+ doc: None,
}
})
}).collect()
path: Some(TomlPath(ex.clone())),
test: None,
plugin: None,
+ doc: None,
}
})
}).collect()
path: Some(TomlPath(ex.clone())),
test: None,
plugin: None,
+ doc: None,
}
})
}).collect()
path: layout.lib.as_ref().map(|p| TomlPath(p.clone())),
test: t.test,
plugin: t.plugin,
+ doc: t.doc,
}
} else {
t.clone()
path: bin.as_ref().map(|&p| TomlPath(p.clone())),
test: t.test,
plugin: None,
+ doc: t.doc,
}
} else {
t.clone()
&summary,
targets.as_slice(),
&Path::new("target"),
+ &Path::new("doc"),
sources,
match project.build {
Some(SingleBuildCommand(ref cmd)) => vec!(cmd.clone()),
crate_type: Option<Vec<String>>,
path: Option<TomlPath>,
test: Option<bool>,
+ doc: Option<bool>,
plugin: Option<bool>,
}
enum TestDep { Needed, NotNeeded }
- fn target_profiles(target: &TomlTarget,
- dep: TestDep) -> Vec<Profile> {
+ fn target_profiles(target: &TomlTarget, dep: TestDep) -> Vec<Profile> {
let mut ret = vec![Profile::default_dev(), Profile::default_release()];
match target.test {
Some(false) => {}
}
+ match target.doc {
+ Some(true) | None => ret.push(Profile::default_doc()),
+ Some(false) => {}
+ }
+
match dep {
Needed => ret.push(Profile::default_test().test(false)),
_ => {}
--- /dev/null
+use support::{project, execs};
+use support::{COMPILING};
+use hamcrest::{assert_that, existing_file, existing_dir, is_not};
+
+fn setup() {
+}
+
+test!(simple {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ "#)
+ .file("src/lib.rs", r#"
+ pub fn foo() {}
+ "#);
+
+ assert_that(p.cargo_process("cargo-doc"),
+ execs().with_status(0).with_stdout(format!("\
+{compiling} foo v0.0.1 (file:{dir})
+",
+ compiling = COMPILING,
+ dir = p.root().display()).as_slice()));
+ assert_that(&p.root().join("target/doc"), existing_dir());
+ assert_that(&p.root().join("target/doc/foo/index.html"), existing_file());
+})
+
+test!(no_build_main {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ "#)
+ .file("src/lib.rs", r#"
+ pub fn foo() {}
+ "#)
+ .file("src/main.rs", r#"
+ bad code
+ "#);
+
+ assert_that(p.cargo_process("cargo-doc"),
+ execs().with_status(0).with_stdout(format!("\
+{compiling} foo v0.0.1 (file:{dir})
+",
+ compiling = COMPILING,
+ dir = p.root().display()).as_slice()));
+})
+
+test!(doc_no_libs {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ "#)
+ .file("src/main.rs", r#"
+ bad code
+ "#);
+
+ assert_that(p.cargo_process("cargo-doc"),
+ execs().with_status(0));
+})
+
+test!(doc_twice {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ "#)
+ .file("src/lib.rs", r#"
+ pub fn foo() {}
+ "#);
+
+ assert_that(p.cargo_process("cargo-doc"),
+ execs().with_status(0).with_stdout(format!("\
+{compiling} foo v0.0.1 (file:{dir})
+",
+ compiling = COMPILING,
+ dir = p.root().display()).as_slice()));
+
+ assert_that(p.cargo_process("cargo-doc"),
+ execs().with_status(0).with_stdout(format!("\
+{compiling} foo v0.0.1 (file:{dir})
+",
+ compiling = COMPILING,
+ dir = p.root().display()).as_slice()));
+})
+
+test!(doc_deps {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#)
+ .file("src/lib.rs", r#"
+ extern crate bar;
+ pub fn foo() {}
+ "#)
+ .file("bar/Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+ "#)
+ .file("bar/src/lib.rs", r#"
+ pub fn bar() {}
+ "#);
+
+ assert_that(p.cargo_process("cargo-doc"),
+ execs().with_status(0).with_stdout(format!("\
+{compiling} bar v0.0.1 (file:{dir})
+{compiling} foo v0.0.1 (file:{dir})
+",
+ compiling = COMPILING,
+ dir = p.root().display()).as_slice()));
+
+ assert_that(&p.root().join("target/doc"), existing_dir());
+ assert_that(&p.root().join("target/doc/foo/index.html"), existing_file());
+ assert_that(&p.root().join("target/doc/bar/index.html"), existing_file());
+})
+
+test!(doc_no_deps {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#)
+ .file("src/lib.rs", r#"
+ extern crate bar;
+ pub fn foo() {}
+ "#)
+ .file("bar/Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+ "#)
+ .file("bar/src/lib.rs", r#"
+ pub fn bar() {}
+ "#);
+
+ assert_that(p.cargo_process("cargo-doc").arg("--no-deps"),
+ execs().with_status(0).with_stdout(format!("\
+{compiling} bar v0.0.1 (file:{dir})
+{compiling} foo v0.0.1 (file:{dir})
+",
+ compiling = COMPILING,
+ dir = p.root().display()).as_slice()));
+
+ assert_that(&p.root().join("target/doc"), existing_dir());
+ assert_that(&p.root().join("target/doc/foo/index.html"), existing_file());
+ assert_that(&p.root().join("target/doc/bar/index.html"), is_not(existing_file()));
+})
+
+test!(doc_only_bin {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#)
+ .file("src/main.rs", r#"
+ extern crate bar;
+ pub fn foo() {}
+ "#)
+ .file("bar/Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+ "#)
+ .file("bar/src/lib.rs", r#"
+ pub fn bar() {}
+ "#);
+
+ assert_that(p.cargo_process("cargo-doc"),
+ execs().with_status(0));
+
+ assert_that(&p.root().join("target/doc"), existing_dir());
+ assert_that(&p.root().join("target/doc/bar/index.html"), existing_file());
+})
mod test_cargo_version;
mod test_cargo_new;
mod test_cargo_compile_plugins;
+mod test_cargo_doc;